/**
 * @file ctrlZee_light.c (gateway)
 * @brief A Z-Stack module that provides an implementation of a light endpoint.
 *        This endpoint represents a light bulb and supports the following:
 *         1) Reporting status (On/Off)
 *         2) Responding to commands (On/Off)
 * @author Vamshi Ponnapalli
 */
/*******************************************************************************
 * Includes
 ******************************************************************************/
#include "ZDObject.h"
#include "ctrlZee.h"

/*******************************************************************************
 * Variable declarations
 ******************************************************************************/
// ----- Constants (macros) -----
#define CTRLZEE_LIGHT_OUT_CLUSTER_COUNT   1
#define CTRLZEE_LIGHT_IN_CLUSTER_COUNT    1

#define CTRLZEE_LIGHT_MAX_DEST_ADDR       1

// ----- Constants (variables) -----
//Output clusters.
const cId_t ctrlZee_lightOutClusters[CTRLZEE_LIGHT_OUT_CLUSTER_COUNT] =
{
  CTRLZEE_CLUSTER_ID_STATUS
};

//Input clusters.
const cId_t ctrlZee_lightInClusters[CTRLZEE_LIGHT_IN_CLUSTER_COUNT] = 
{
  CTRLZEE_CLUSTER_ID_ONOFF
};

//Simple descriptor
const SimpleDescriptionFormat_t ctrlZee_lightSimpleDesc =
{
  CTRLZEE_ENDPOINT_LIGHT,
  CTRLZEE_PROFILE_ID,
  CTRLZEE_DEVICE_ID_LIGHT,
  CTRLZEE_DEVICE_VERSION,
  CTRLZEE_FLAGS_DEFAULT,

  //Input clusters
  CTRLZEE_LIGHT_IN_CLUSTER_COUNT,
  (cId_t *)ctrlZee_lightInClusters,

  //Output clusters
  CTRLZEE_LIGHT_OUT_CLUSTER_COUNT,
  (cId_t *)ctrlZee_lightOutClusters
};

// ----- Global variables -----

//Endpoint descriptor
endPointDesc_t ctrlZee_lightEndpointDesc;

//Task id of the ctrlzee module.
byte ctrlZee_lightTaskId;   

//Holds the current network state of the ctrlzee module.
devStates_t ctrlZee_lightNetworkState;

//Ctrl-Zee specific message counter.
byte ctrlZee_lightMessageId;
byte ctrlZee_lightControlTaskId;

//Light status.
byte ctrlZee_lightStatus;

//Variable to hold the destination address for Zigbee communication
afAddrType_t ctrlZee_lightDestAddress[CTRLZEE_LIGHT_MAX_DEST_ADDR];

/*******************************************************************************
 * Static function declarations (private to this file)
 ******************************************************************************/
static void ctrlZee_lightProcessZDOMessage(zdoIncomingMsg_t *inMsg);
static void ctrlZee_lightProcessIncomingMessage(afIncomingMSGPacket_t *packet);
static void ctrlZee_lightSendMessage(afAddrType_t* destAddress, 
                                       ctrlzee_peer_cmd_t *message, 
                                       byte size);
static void ctrlZee_lightSendPeerStatus(void);
static void ctrlZee_lightSendZDOMatchRequest(void);
/*******************************************************************************
 * Function implementations
 ******************************************************************************/
void ctrlZee_lightInit(byte taskId)
{
  //Basic initialization
  ctrlZee_lightTaskId = taskId;
  ctrlZee_lightNetworkState = DEV_INIT;
  ctrlZee_lightMessageId = 0;
  ctrlZee_lightStatus = 0;

  //Register with control
  ctrlZee_lightControlTaskId = 
                            ctrlZee_registerLightEndpoint(ctrlZee_lightTaskId);
  
  //Initialize destination address(es)
  for(uint8 index=0; index < CTRLZEE_LIGHT_MAX_DEST_ADDR; index++)
  {
    ctrlZee_lightDestAddress[index].addrMode = (afAddrMode_t)AddrNotPresent;
    ctrlZee_lightDestAddress[index].endPoint = 0;
    ctrlZee_lightDestAddress[index].addr.shortAddr = 0;
  }
  //Fill out the endpoint description.
  ctrlZee_lightEndpointDesc.endPoint = CTRLZEE_ENDPOINT_LIGHT;
  ctrlZee_lightEndpointDesc.task_id = &ctrlZee_lightTaskId;
  ctrlZee_lightEndpointDesc.simpleDesc
            = (SimpleDescriptionFormat_t *)&ctrlZee_lightSimpleDesc;
  ctrlZee_lightEndpointDesc.latencyReq = noLatencyReqs;

  //Register the endpoint description with the AF
  afRegister(&ctrlZee_lightEndpointDesc);
  
  //Register for ZDO messages.  
  ZDO_RegisterForZDOMsg(ctrlZee_lightTaskId, Match_Desc_rsp);
}
/******************************************************************************/
UINT16 ctrlZee_lightProcessEvent(byte taskId, UINT16 events)
{
  afIncomingMSGPacket_t *packet;
  (void)taskId;  // Intentionally unreferenced parameter

  //Check if the a system event was raised.
  if (events & SYS_EVENT_MSG)
  {
    //Read the incoming message(s).
    packet = (afIncomingMSGPacket_t *)osal_msg_receive(ctrlZee_lightTaskId);

    //Loop through all incoming messages.
    while (packet)
    {
      switch (packet->hdr.event)
      {
          //Callback message from the ZDO layer.
        case ZDO_CB_MSG:
          ctrlZee_lightProcessZDOMessage((zdoIncomingMsg_t *)packet);
          break;
          
          //Confirmation of a message packet sent.
        case AF_DATA_CONFIRM_CMD:
          // Action taken when confirmation is received.
          if (((afDataConfirm_t*)packet)->hdr.status != ZSuccess)
          {
            //TODO: Do something here.
          }
          break;

          //Incoming message.
        case AF_INCOMING_MSG_CMD:
           ctrlZee_lightProcessIncomingMessage(packet);
          break;

          //Change of network state of the device.
        case ZDO_STATE_CHANGE:
          ctrlZee_lightNetworkState = (devStates_t)(packet->hdr.status);
          
          if ((ctrlZee_lightNetworkState == DEV_ZB_COORD)
              || (ctrlZee_lightNetworkState == DEV_ROUTER)
              || (ctrlZee_lightNetworkState == DEV_END_DEVICE))
          {
            // Start reporting status of the light.
            osal_start_timerEx(ctrlZee_lightTaskId,
                               CTRLZEE_OSAL_EVENT_REPORT_STATUS,
                               CTRLZEE_OSAL_REPORT_STATUS_DURATION);
          }
          break;

        default:
          break;
      }

      //Release the memory.
      osal_msg_deallocate((uint8 *)packet);

      //Read next message in the chain.
      packet = (afIncomingMSGPacket_t *)osal_msg_receive(ctrlZee_lightTaskId);
    }
    // return unprocessed events
    return (events ^ SYS_EVENT_MSG);
  }
  
  /**
   * "Report status" event received.
   */
  if (events & CTRLZEE_OSAL_EVENT_REPORT_STATUS)
  {
    //Send the current status of the light.
    ctrlZee_lightSendPeerStatus();
    
    //Setup to send a ping again after a delay.
    osal_start_timerEx(ctrlZee_lightTaskId,
                       CTRLZEE_OSAL_EVENT_REPORT_STATUS,
                       CTRLZEE_OSAL_REPORT_STATUS_DURATION);
    
    // return unprocessed events
    return (events ^ CTRLZEE_OSAL_EVENT_REPORT_STATUS);
  }
    
  /**
   * "Scan devices" event received.
   */
  if(events & CTRLZEE_OSAL_EVENT_SCAN_DEVICES)
  { 
    //Scan other devices for a match.
    ctrlZee_lightSendZDOMatchRequest();
    
    // return unprocessed events
    return (events ^ CTRLZEE_OSAL_EVENT_SCAN_DEVICES);
  }
  
  // Discard unknown events
  return 0;
}
/******************************************************************************/
static void ctrlZee_lightProcessZDOMessage(zdoIncomingMsg_t *inMsg)
{
  switch (inMsg->clusterID)
  {
    case Match_Desc_rsp:
      {
        ZDO_ActiveEndpointRsp_t *pRsp = ZDO_ParseEPListRsp(inMsg);
        if (pRsp)
        {
          //First find an open slot to store this address.
          afAddrType_t *destAddress = 0x00;          
          for(uint8 index=0; index < CTRLZEE_LIGHT_MAX_DEST_ADDR; index++)
          {
            if(ctrlZee_lightDestAddress[index].addrMode == afAddrNotPresent)
            {
              destAddress = &ctrlZee_lightDestAddress[index];
            }
            else if(ctrlZee_lightDestAddress[index].addr.shortAddr == 
                                                                  pRsp->nwkAddr)
            {
              destAddress = &ctrlZee_lightDestAddress[index];
              break;
            }
          }
          
          //Check if an open slot was found.
          if(destAddress != 0x00)
          {
            if (pRsp->status == ZSuccess && pRsp->cnt)
            {
              destAddress->addrMode = (afAddrMode_t)Addr16Bit;
              destAddress->addr.shortAddr = pRsp->nwkAddr;
              
              // Keep it simple, and pick up the first endpoint
              destAddress->endPoint = pRsp->epList[0];
            }
          }
          osal_mem_free(pRsp);
        }
      }
      break;
  }
}
/******************************************************************************/
static void ctrlZee_lightProcessIncomingMessage(afIncomingMSGPacket_t *pkt)
{
  ctrlzee_peer_cmd_t* message;
    
  switch (pkt->clusterId)
  {
  case CTRLZEE_CLUSTER_ID_ONOFF:
    message = (ctrlzee_peer_cmd_t*)pkt->cmd.Data;
    switch(message->command)
    {
    case CTRLZEE_PEER_CMD_ONOFF:
      //Update the status of the light
      ctrlZee_lightStatus = message->arg1;
      
      //TODO: Toggle hardware signal to actuall turn on/off the light.
      break;
    }    
    break;
  }
}
/******************************************************************************/
static void ctrlZee_lightSendMessage(afAddrType_t* destAddress, 
                                       ctrlzee_peer_cmd_t *message, 
                                       byte size)
{
  //Don't send message if the short address has not been initialized.
  if(destAddress->addr.shortAddr == 0x00)
  {
    return;
  }
  if (AF_DataRequest(destAddress,
                     &ctrlZee_lightEndpointDesc,
                     CTRLZEE_CLUSTER_ID_COMMAND,
                     size,
                     (byte *)message,
                     &ctrlZee_lightMessageId,
                     AF_DISCV_ROUTE,
                     AF_DEFAULT_RADIUS) == afStatus_SUCCESS)
  {
    //TODO: Do something here.
  }
  else
  {
    //TODO: Do something here.
  }
}
/******************************************************************************/
static void ctrlZee_lightSendPeerStatus(void)
{
  //CtrlZee message structure
  ctrlzee_peer_cmd_t message = {CTRLZEE_PEER_CMD_STATUS
                                 ,ctrlZee_lightStatus
                                 ,0x00
                                 ,0x00};
 
  // Send the status.
  for(uint8 index=0; index < CTRLZEE_LIGHT_MAX_DEST_ADDR; index++)
  {
    ctrlZee_lightSendMessage(&ctrlZee_lightDestAddress[index], 
                             &message, 
                             4);
  }
}
/******************************************************************************/
static void ctrlZee_lightSendZDOMatchRequest(void)
{
  // Initiate a Match Description Request (Service Discovery)
  zAddrType_t dstAddr;
  dstAddr.addrMode = AddrBroadcast;
  dstAddr.addr.shortAddr = NWK_BROADCAST_SHORTADDR;
  ZDP_MatchDescReq(&dstAddr,
                   NWK_BROADCAST_SHORTADDR,
                   CTRLZEE_PROFILE_ID,
                   CTRLZEE_LIGHT_OUT_CLUSTER_COUNT, 
                   (cId_t *)ctrlZee_lightOutClusters,
                   CTRLZEE_LIGHT_IN_CLUSTER_COUNT, 
                   (cId_t *)ctrlZee_lightInClusters,
                   FALSE);
}
/******************************************************************************/
